import {redisSQLDatabase} from 'TYPE'
import {cache} from '../cache'
import knex from '../sql'
import {postDB} from './post.db'
import {userDB} from './user.db'

declare module 'knex/types/tables' {
    interface UserPostRelation {
        uid: number
        pid: number
        relationship: string
        value: string
        update_time: Date
    }
    interface Tables {
        user_post_relations: UserPostRelation
    }
}

/**
 * user <--relation--> post
 * A relationship between user to post.
 * 
 */
class UserPostRelationDB implements redisSQLDatabase {
    relationships = ['star', 'collect', 'vote']
    static keys(uid: number, pid: number, relationship: string) {
        return [`relation:${relationship}:user:${uid}`, `relation:${relationship}:post:${pid}`]
    }
    async queryRelationsByUid(uid: number, relationship: string, paged: number, posts_per_page: number) {
        let key = UserPostRelationDB.keys(uid, 0, relationship)[0]
        let uids: string[] = await cache.ZRANGE(key, 0, '+inf',
            {
                BY: "SCORE",
                LIMIT: {
                    offset: (paged - 1) * posts_per_page,
                    count: posts_per_page
                }
            }) // redis zset
        return uids
    }
    async queryRelationsByPid(pid: number, relationship: string, paged: number, posts_per_page: number) {
        let key = UserPostRelationDB.keys(0, pid, relationship)[1]
        let uids: string[] = await cache.ZRANGE(key, 0, '+inf',
            {
                BY: "SCORE",
                LIMIT: {
                    offset: (paged - 1) * posts_per_page,
                    count: posts_per_page
                }
            }) // redis zset
        return uids
    }
    async queryRelationsCountByUid(uid: number, relationship: string) {
        let key = UserPostRelationDB.keys(uid, 0, relationship)[0]
        return cache.ZCARD(key)
    }
    async queryRelationsCountByPid(pid: number, relationship: string) {
        let key = UserPostRelationDB.keys(0, pid, relationship)[1]
        return cache.ZCARD(key)
    }
    async hasRelation(uid: number, pid: number, relationship: string) {
        let key = UserPostRelationDB.keys(uid, pid, relationship)[1]
        let flag: boolean = !! await cache.ZSCORE(key, String(uid))
        return flag
    }
    async addRelation(uid: number, pid: number, relationship: string) {
        if (await this.hasRelation(uid, pid, relationship)) {
            return true
        }
        let postAuthor = (await postDB.getPostById(pid)).author
        userPostRelationDB.achievementIncBy(postAuthor, relationship, 1)
        let flag = false
        let update_time = new Date()
        await knex('user_post_relations').insert({uid, pid, relationship, update_time, value: 'true'})
            .onConflict().merge()
            .then(ids => {

                if (ids.length > 0) {
                    flag = true
                    const [key1, key2] = UserPostRelationDB.keys(uid, pid, relationship)
                    cache.ZADD(key1, {value: String(pid), score: update_time.getTime()})
                    cache.ZADD(key2, {value: String(uid), score: update_time.getTime()})
                }
            })

        return flag
    }
    async loadAll() {
        await cache.keys('relation:*').then(async (keys) => {
            for (const key of keys) {
                await cache.del(key)
            }
        })
        await knex('user_post_relations').select().then(rs => {
            for (const r of rs) {
                if (!r.value || r.value === 'false') continue
                const {uid, pid, relationship, update_time} = r
                const [key1, key2] = UserPostRelationDB.keys(uid, pid, relationship)
                postDB.getPostById(pid).then(post => {
                    userPostRelationDB.achievementIncBy(post.author, relationship, 1)
                })
                cache.ZADD(key1, {value: String(pid), score: update_time.getTime()})
                cache.ZADD(key2, {value: String(uid), score: update_time.getTime()})
            }
        })
    }
    async removeRelation(uid: number, pid: number, relationship: string) {
        if (! await this.hasRelation(uid, pid, relationship)) {
            return true
        }
        const [key1, key2] = UserPostRelationDB.keys(uid, pid, relationship)
        cache.ZREM(key1, String(pid))
        cache.ZREM(key2, String(uid))
        let flag = false
        let postAuthor = (await postDB.getPostById(pid)).author
        userPostRelationDB.achievementIncBy(postAuthor, relationship, -1)
        await knex('user_post_relations').where({uid, pid, relationship}).update({value: 'false'}).then(num => {
            if (num > 0) flag = true
        })
        return flag
    }
    async initAchievement(uid: number) {
        let achievement = {} as any
        for (const r of userPostRelationDB.relationships) {
            achievement[r + '_count'] = 0
        }
        return cache.json.set(`achievement:` + uid, '$', achievement)
    }
    protected async achievementIncBy(uid: number, relationship: string, amount: number) {
        return cache.json.NUMINCRBY(`achievement:` + uid, `$.${relationship}_count`, amount)
    }
    async getAchievementByUid(uid: number) {
        return cache.json.get(`achievement:` + uid)
    }
}
export const userPostRelationDB = new UserPostRelationDB()
